/* TCSS 590A - Cybernetics and Systems
 * Group Project - Modeling
 * By Barry Jones, Naren Meadem, Michael Pitts, Yuan Tao
 * Fall 2012, University of Washington, Tacoma
 */
package model.delayed.tactical;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import model.delayed.Event;
import model.delayed.strategic.StrategicController;


/**
 * A ShouldNegotiate class that assumes the generator will not allow a successful 
 * negotiation except after so many steps.  It then uses an optimal stopping rule 
 * calculation to find the optimal value to negotiate on.  It assumes the values 
 * generated are evenly distributed.
 * @author Michael Pitts
 * @version Dec 3, 2012
 */
public class RareShouldNegotiate implements ShouldNegotiate
{
	/** Provides the max and average values of all Events over time.*/
	private final NumFinder my_mean_finder;
	
	/** How many steps into the future to consider.*/
	private final int my_steps;

	/**
	 * Creates a new ShouldNegotiate object that assumes Event values are evenly 
	 * distributed and that only one Event in a certain number is negotiable.
	 * @param the_mean_finder finds the mean and max of Event values.
	 * @param the_steps are the number of time steps between successful negotiations.
	 */
	public RareShouldNegotiate(final NumFinder the_mean_finder, 
			final int the_steps)
	{
		my_mean_finder = the_mean_finder;
		my_steps = the_steps;
	}

	@Override public Map<Event, Long> negotiate(long the_time,
			Collection<Event> the_events)
	{
		Event best = null;
		final long optimal = findOptimalStopping();
		if (optimal < 0 && the_events.size() > 0)
		{
			best = getSmallest(the_events);
			if (best.value > optimal) best = null;
		} else if (optimal > 0 && the_events.size() > 0)
		{
			best = getLargest(the_events);
			if (best.value < optimal) best = null;
		}
		final Map<Event, Long> map = new HashMap<Event, Long>();
		if (best != null && best.value < 0) map.put(best, -best.value);
		if (best != null && best.value > 0) map.put(best, best.value);
		return map;
	}
	
	/**
	 * Searches a Collection of Events and finds the one with the largest value.
	 * @param the_events is the Collection of Events to search.
	 * @return the Event with the largest value.
	 */
	private Event getLargest(final Collection<Event> the_events)
	{
		Event best = the_events.iterator().next();
		for (Event e : the_events) if (e.value > best.value) best = e;
		return best;
	}
	
	/**
	 * Gets the smallest valued Event from the Collection.
	 * @param the_events is the Collection of Events to search.
	 * @return the Event with the smallest value.
	 */
	private Event getSmallest(final Collection<Event> the_events)
	{
		Event best = the_events.iterator().next();
		for (Event e : the_events) if (e.value < best.value) best = e;
		return best;
	}

	/**
	 * Gets the optimal stopping value from the mean finder.
	 * @return the optimal stopping value, given my_steps.
	 */
	private long findOptimalStopping()
	{
		final long max = my_mean_finder.getMax();
		double val = my_mean_finder.getMean();
		for (int i = 0; i < my_steps; i++) val = (max*max + val*val) / (2*max);
		return Math.round(val);
	}

	/**
	 * A NumFinder provides the functionality to get the average and max of some
	 * data.
	 * @author Michael Pitts
	 * @version Dec 4, 2012
	 */
	public interface NumFinder
	{
		/**
		 * Gets the mean, or average, of the Events so far.
		 * @return the average value of all previous and current Events.
		 */
		public long getMean();
		
		/**
		 * Gets the maximum magnitude of the Events so far.
		 * @return the maximum magnitude of all Events.
		 */
		public long getMax();
	}
	
	/**
	 * Gets a NumFinder that provides the average and maximum of cost Events.
	 * @param the_controller is the StrategicController that is modeling the 
	 * cost Events.
	 * @return a new NumFinder.
	 */
	public static final NumFinder getCostFinder(final StrategicController the_controller)
	{
		return new NumFinder() {
			@Override public long getMean() {return the_controller.getMeanCost();}
			@Override public long getMax() {return the_controller.getMinCost();}
		};
	}
	
	/**
	 * Gets a NumFinder that provides the average and maximum of wage Events.
	 * @param the_controller is the StrategicController that models the wage Events.
	 * @return a new NumFinder.
	 */
	public static final NumFinder getWageFinder(final StrategicController the_controller)
	{
		return new NumFinder() {
			@Override public long getMean() {return the_controller.getMeanWage();}
			@Override public long getMax() {return the_controller.getMaxWage();}
		};
	}
}
